package com.example.file.security.config;


import com.example.file.exception.ServiceException;
import com.example.file.response.ResultEnum;
import com.example.file.security.AuthenticationManager;
import com.example.file.security.support.JwtVerifyHandler;
import com.example.file.security.support.ServerHttpBearerAuthenticationConverter;
import com.example.file.security.support.ServerHttpCookieAuthenticationConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity;
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
import reactor.core.publisher.Mono;

/**
 * WebSecurityConfig class
 *
 * @author Erik Amaru Ortiz
 */
@Configuration
@EnableReactiveMethodSecurity
public class WebSecurityConfig {
    private final Logger logger = LoggerFactory.getLogger(WebSecurityConfig.class);


    @Value("${app.public_routes}")
    private String[] publicRoutes;

    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http, AuthenticationManager authManager) {
        return http
                .authorizeExchange()
                // 放行静态资源路径
                .pathMatchers(HttpMethod.OPTIONS)
                .permitAll()
                .pathMatchers(publicRoutes)
                .permitAll()
                .anyExchange()
                .authenticated()
                .and()
                .csrf()
                .disable()
                .httpBasic()
                .disable()
                .formLogin()
                .disable()
                .exceptionHandling()
                .authenticationEntryPoint((swe, e) -> {
                    logger.info("[1] Authentication error: Unauthorized[401]: " + e.getMessage());

                    return Mono.fromRunnable(() -> swe.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED));
                })
                .accessDeniedHandler((swe, e) -> {
                    logger.info("[2] Authentication error: Access Denied[401]: " + e.getMessage());

                    return Mono.fromRunnable(() -> swe.getResponse().setStatusCode(HttpStatus.FORBIDDEN));
                })
                .and()
                .addFilterAt(bearerAuthenticationFilter(authManager), SecurityWebFiltersOrder.AUTHENTICATION)
                .addFilterAt(cookieAuthenticationFilter(authManager), SecurityWebFiltersOrder.AUTHENTICATION)
                .exceptionHandling()
                .authenticationEntryPoint((swe, e) -> {
                    logger.info("[1] Authentication error: Unauthorized[401]: " + e.getMessage());
                    throw new ServiceException(ResultEnum.UNAUTHORIZED);
                })
                .accessDeniedHandler((swe, e) -> {
                    logger.info("[2] Authentication error: Access Denied[401]: " + e.getMessage());

                    return Mono.fromRunnable(() -> swe.getResponse().setStatusCode(HttpStatus.FORBIDDEN));
                }).and()
                .build();
    }

    /**
     * Spring安全性通过过滤器链接工作。我们需要向链中添加一个JWT自定义过滤器。
     * 什么是AuthenticationWebFilter:一个执行特定请求身份验证的WebFilter。
     * 逻辑概要:一个请求进来，如果它不匹配setRequiresAuthenticationMatcher (ServerWebExchangeMatcher)，那么这个过滤器什么都不做，继续WebFilterChain。
     * 如果匹配，那么..尝试将ServerWebExchange转换为身份验证。如果结果为空，则过滤器不再执行任何操作，并继续WebFilterChain。如果它确实创建了一个Authentication…
     * 使用AuthenticationWebFilter(以下简称ReactiveAuthenticationManager)中配置的ReactiveAuthenticationManager进行认证。
     * 如果身份验证成功，将调用ServerAuthenticationSuccessHandler，并在ReactiveSecurityContextHolder上设置authenticationl，否则将调用ServerAuthenticationFailureHandler
     */
    AuthenticationWebFilter bearerAuthenticationFilter(AuthenticationManager authManager) {
        AuthenticationWebFilter bearerAuthenticationFilter = new AuthenticationWebFilter(authManager);
        bearerAuthenticationFilter.setAuthenticationConverter(new ServerHttpBearerAuthenticationConverter(new JwtVerifyHandler()));
        bearerAuthenticationFilter.setRequiresAuthenticationMatcher(ServerWebExchangeMatchers.pathMatchers("/**"));

        return bearerAuthenticationFilter;
    }

    AuthenticationWebFilter cookieAuthenticationFilter(AuthenticationManager authManager) {
        AuthenticationWebFilter cookieAuthenticationFilter = new AuthenticationWebFilter(authManager);
        cookieAuthenticationFilter.setAuthenticationConverter(new ServerHttpCookieAuthenticationConverter(new JwtVerifyHandler()));
        cookieAuthenticationFilter.setRequiresAuthenticationMatcher(ServerWebExchangeMatchers.pathMatchers("/**"));

        return cookieAuthenticationFilter;
    }
}
